$.extend($.ds.modules, {
	/***************************
	 **    Templates Module   **
	 ***************************/
	templates : {
		load : function() { 
			$.ds.templates = new $.ds.modules.templates.template_interface();
			$.extend( $.ds.fn, $.ds.modules.templates.user_interface );
			$.extend( $.fn, $.ds.modules.templates.jquery_interface );
		},
		unload : function() { 
			delete $.ds.templates;
		},
		
		user_interface : {
			render : function(templateName, targetParent) {
				$.ds.debug.assert($.ds.templates.has(templateName), "Template '" + templateName + "' Not Found");
				var $p = $(targetParent);
				var t = $.ds.templates.get(templateName);
				
				this.each(function(node, i) {
					var r = $.ds.templates.__compile_element(t.element, node, false);
					for(var i = 0; i < r.length; i++) {
						$p.append(r[i]);
					}
				});
			}
		},
		jquery_interface : {
			fieldeditor : function() {
				if(arguments.length == 1) {
					var options = arguments[0];
					var init   = (options.init === undefined ? function () {} : options.init);
					var update = (options.update === undefined ? function () {} : options.update);
					var path   = options.path;
					var value  = options.value;
					
					$(this).each(function() {
						$(this).data( {
							fieldEditorUpdate : update,
							fieldEditorPath : path
						}).addClass('fieldeditor');
						if(options.init.constructor == Function) {
							options.init.apply(this);
						}
						if(options.value !== undefined) {
							$(this).fieldeditor('update', options.value);
						}
						
						var fe = this;
						$(document).bind("commitdatastore", function(e, changeset) {
							var path = $(fe).data("fieldEditorPath");
							if(path != "" && changeset[path] !== undefined) {
								var value = $.ds.storage.getValue(path);
								$(fe).fieldeditor('update', value);
							}
						});
					});
				}
				else if(arguments.length == 2) {
					var name = arguments[0];
					var value = arguments[1];
					switch(name) {
						case 'update' :
							$(this).each(function() {
								var fe = this;
								if($(this).hasClass('fieldeditor') == false) {
									fe = $(this).parents('.fieldeditor').eq(0);
								}
								var update = $(fe).data('fieldEditorUpdate');
								if(update.constructor == Function) {
									update.apply(fe, [value]);
								}
							});
							break;
						case 'commit' :
							$(this).each(function() {
								var fe = this;
								if($(this).hasClass('fieldeditor') == false) {
									fe = $(this).parents('.fieldeditor').eq(0);
								}
								var path = $(fe).data('fieldEditorPath');
								$.ds.storage.transaction(function() {
									$.ds.storage.setValue(path, value);
								});
							});
							break;
					}
				}
			}
		},
		template_interface : function() {
			this.has = function(name) {
				var path = ["@templates", name].join(".");
				return $.ds.storage.hasValue(path);
			},
			this.add = function(name, tmpl, init, update) {
				var template = $(tmpl);
				var path = ["@templates", name].join(".");
				var markup = template.get(0).outerHTML;
				var initSource = (init === undefined ? "function() { }" : init.toString());
				var updateSource = (update === undefined ? "function(value) { }": update.toString());
				
				$.ds.storage.transaction(function() {
					$.ds.storage.setValue(path, {
						html : markup,
						initSource : initSource,
						updateSource : updateSource
					});
				});
			},
			this.get = function(name) {
				var path = ["@templates", name].join(".");
				var tmpl = null;
				$.ds.storage.transaction(function() {
					tmpl = $.ds.storage.getValue(path);
				});
				tmpl.element = $(tmpl.html).get(0);
				eval("tmpl.init = " +  tmpl.initSource);
				eval("tmpl.update = " +  tmpl.updateSource);
				
				return tmpl;
			},
			this.loadTemplates = function() {
				var t = this;
				$("object[type='text/html'][name]").each( function() {
					var name = $(this).attr("name");
					//TODO: Allow remote asset loading via AJAX
					var tmpl = $(this).children(0).get(0);
					var init = undefined;
					var update = undefined;
					
					$("script[type='init']", this).each(function() {
						eval("init = function(value) { " + $(this).text() + "};");
					});
					$("script[type='update']", this).each(function() {
						eval("update = function(value) { " + $(this).text() + "};");
					});
					
					t.add(name, tmpl, init, update);
				});
				$("*[is-template]").each( function() {
					var name = $(this).attr("is-template");
					var tmpl = $(this).get(0);
					t.add(name, tmpl);
				});
			},
			
			this.__compile_element = function(element, node, nested) {
				var fe = element.attributes.getNamedItem("for-each");
				var ut = element.attributes.getNamedItem("use-template");
				var wn = element.attributes.getNamedItem("with-node");
				var ui = element.attributes.getNamedItem("use-if");
				
				var init = undefined;
				var update = undefined;
				
				if(ut != null) { 
					var tmpl = $.ds.templates.get( ut.value );
					element = tmpl.element;
					init = tmpl.init;
					update = tmpl.update;
				}
				if(wn != null) { node = node.gotoPath( wn.value ); }
				
				if(ui != null) {
					if(! $.ds.query.test(node, ui.value, {}) ) {
						return [];
					}
				}
				
				if(fe != null && !nested) {
					var nodes = $.ds.query.execute(node, fe.value);
					var results = [];
					for(var i = 0; i < nodes.length; i++) {
						results = results.concat( this.__compile_element(element, nodes[i], true) );
					}
					return results;
				}
				
				var compiled_result = document.createElement(element.nodeName);
				
				//Process Attributes
				for(var i = 0; i < element.attributes.length; i++) {
					var value = element.attributes[i].value;
					var name = element.attributes[i].name;
					
					value = this.__compile_text(value, node);
					compiled_result.setAttribute(name, value);
				}
				
				//Process Child Nodes
				for(var i = 0; i < element.childNodes.length; i++) {
					var cn = element.childNodes[i];
					switch(cn.nodeType) {
						case 3 : //TEXTNODE
							var t = this.__compile_text(cn.nodeValue, node);
							compiled_result.appendChild( document.createTextNode(t) );
							break;
						case 1 : //ELEMENTNODE
							var n = this.__compile_element(cn, node, false);
							for(var j = 0; j < n.length; j++) {
								var childElement = n[j];
								compiled_result.appendChild( childElement );
							}
							break;
					}
				}
				
				if(init !== undefined || update !== undefined) {
					$(compiled_result).fieldeditor({
						path : node.path(),
						value : node.value(),
						init : init,
						update : update
					})
				}
				
				return [compiled_result];
			},
			
			this.__compile_text = function(text, node) {
				var node_value = function(str, p1, offset, s) {
					switch(p1) {
						case "$name"  : return node.name();
						case "$path"  : return node.path();
						case "$value" : return node.value();
					}
					return node.get(p1).value();
				}
				return text.replace(/[{]{2}\s*(.*?)\s*[}]{2}/g, node_value);
			}
		}
	}
});